package hxgo

import (
	"strings"
	"path"
	"reflect"
)

// router object
type Router struct {
	routes []*Route
}

// append route into router
func (this *Router) Append(rt *Route){
	this.routes = append(this.routes,rt)
}

// dispatch request with rules in router
func (this *Router) Dispatch(w *Response, r *Request, v *View) bool {
	for _, route := range this.routes {
		result := route.Call(w, r, v)
		if result == true {
			return true
		}
	}
	return false
}

// route rule object
type Route struct {
	url     string
	method  string
	ext     string
	handler reflect.Type
}

// set route method
func (this *Route) Method(methods string) *Route {
	this.method = strings.ToUpper(methods)
	return this
}

// set route suffix
func (this *Route) Suffix(sfx string) *Route {
	this.ext = strings.ToLower(sfx)
	return this
}

// check route is matched
func (this *Route) is(url string, method string) bool {
	if !strings.HasPrefix(url, this.url) {
		return false
	}
	if this.method != "ALL" {
		if !strings.Contains(this.method, method) {
			return false
		}
	}
	if this.ext != "" {
		ext := path.Ext(url)
		if !strings.Contains(this.ext, ext) {
			return false
		}
	}
	return true
}

// get url param
func (this *Route) getParam(url string) []string {
	ext := path.Ext(url)
	if ext != "" {
		url = strings.Replace(url, ext, "", -1)
	}
	url = strings.Replace(url, this.url, "", 1)
	tmp := strings.Split(url, "/")
	var param []string
	for _, t := range tmp {
		if len(t) > 0 {
			param = append(param, t)
		}
	}
	return param
}

// get calls from param
func (this *Route) getCalls(param []string, method string) []string {
	calls := make([]string, 0)
	if len(param) >= 2 && param[1] != "" {
		calls = append(calls, strings.Title(param[0]) + strings.Title(param[1]) + method)
	}
	if len(param) >= 1 && param[0] != "" {
		calls = append(calls, strings.Title(param[0]) + method)
	}
	calls = append(calls, "Index" + method)
	calls = append(calls, "Index")
	return calls
}

// call route method
func (this *Route) Call(w *Response, r *Request, v *View) bool {
	if !this.is(r.Url, r.Method) {
		return false
	}
	param := this.getParam(r.Url)
	calls := this.getCalls(param, r.Method)

	r.Param = param

	rType := this.handler
	if rType == nil {
		return false
	}
	rValue := reflect.New(rType)
	// do init
	rInit := rValue.MethodByName("New")
	initArgs := make([]reflect.Value, 3)
	initArgs[0] = reflect.ValueOf(w)
	initArgs[1] = reflect.ValueOf(r)
	initArgs[2] = reflect.ValueOf(v)
	rInit.Call(initArgs)

	rInit2 := rValue.MethodByName("Init")
	init2Args := make([]reflect.Value, 0)
	rInit2.Call(init2Args)

	if w.IsDone {
		return true
	}

	for _, call := range calls {
		rMethod := rValue.MethodByName(call)
		if !rMethod.IsValid() {
			continue
		}
		// do called method
		args := make([]reflect.Value, 0)
		rMethod.Call(args)
		break
	}
	return true
}

// define route rule handler interface
type RouteHandler interface {
	New(w *Response, r *Request, v *View)
	Init()
	Index()
}

//-------------------

// create new router
func NewRouter() *Router {
	r := &Router{}
	r.routes = make([]*Route, 0)
	return r
}

// create route rule
func NewRoute(url string, handler RouteHandler) *Route {
	r := &Route{}
	r.url = url
	r.method = "ALL"
	if handler != nil {
		r.handler = reflect.Indirect(reflect.ValueOf(handler)).Type()
	}
	return r
}

